package com.ruoyix.weblog.service.impl;

import cn.hutool.core.io.FileUtil;
import com.ruoyix.common.core.domain.R;
import com.ruoyix.weblog.domain.UploadPO;
import com.ruoyix.weblog.service.IManageVideoService;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.util.Arrays;


@Service

public class ManageVideoServiceImpl implements IManageVideoService {

    @Override
    public R<String> checkExists(String fileMd5) {
        String fileUploadDir = "D:"+File.separator+"uploadTest" +File.separator+ "video"+File.separator+fileMd5;
        //判断文件是否已被上传
        String videoFile = fileUploadDir + File.separator + fileMd5 + ".mp4";
        File file = new File(videoFile);
        if (file.exists()) {
            return R.ok("文件已存在，秒传成功");
        }
        return R.ok();
    }

    @Override
    public R<String> uploadChunk(UploadPO uploadPO) {
        String fileUploadTempDir ="D:"+File.separator+"uploadTest" + File.separator+"videotmp";
        String fileUploadDir ="D:"+File.separator+"uploadTest" +File.separator+ "video";
        // 获得文件分片数据
        MultipartFile fileData = uploadPO.getFile();
        // 分片第几片
        int index = uploadPO.getChunkNumber();
        //总分片数
        int totalChunk = uploadPO.getTotalChunks();
        // 文件md5标识
        String fileMd5 = uploadPO.getIdentifier();
        //判断文件是否已被上传
        String videoFile = fileUploadDir +File.separator+fileMd5+File.separator+  fileMd5 + ".mp4";
        File file = new File(videoFile);
        if (file.exists()) {
            return R.ok("文件已存在，秒传成功");
        }
        String newName = fileMd5 + index + ".tem";
        File uploadFile = new File(fileUploadTempDir + File.separator + fileMd5, newName);
        if (!uploadFile.getParentFile().exists()) {
            uploadFile.getParentFile().mkdirs();
        }
        try {
            fileData.transferTo(uploadFile);
            // 判断总分片数是否等于当前目录下的分片文件数量
            int currentChunkCount = getChunkCount(fileUploadTempDir + File.separator + fileMd5);
            if (totalChunk == currentChunkCount) {
                // 调用合并方法
                merge(fileMd5, fileUploadTempDir, fileUploadDir);
                //根据运行环境分别调用ffmpeg
                String os = System.getProperty("os.name").toLowerCase();
                String m3u8Dir = fileUploadDir + File.separator+"m3u8Dir"+File.separator + fileMd5;
                File m3u8FileDir = new File(m3u8Dir);
                if (!m3u8FileDir.exists()) {
                    m3u8FileDir.mkdirs();
                }
                //计算视频总时长和视频大小，确定视频的分段时长
                String mp4File = fileUploadDir +File.separator+fileMd5+File.separator+ fileMd5 + ".mp4";
                //每个2M分片的毫秒数
                long duration = getVideoDuration(mp4File);
                // 异步执行视频拆分
                if (os.contains("win")) {
                    convertToM3U8ForWindows( mp4File, m3u8Dir, duration);
                } else {
                    convertToM3U8ForLinux( mp4File, m3u8Dir, duration);
                }
            }
            //执行成功返回 url
            return R.ok();
        } catch (IOException e) {
//            log.error("上传视频失败：{}", e.toString());
            FileUtil.del(fileUploadTempDir + File.separator + fileMd5); //删除临时文件
            FileUtil.del(videoFile); //删除视频源文件
            FileUtil.del(fileUploadDir + File.separator + fileMd5); //删除分段ts视频
            return R.fail( "上传视频失败");
        } catch (Exception e) {
//            log.error("视频切割时计算分段时长失败：{}", e.toString());
            FileUtil.del(fileUploadTempDir + File.separator + fileMd5); //删除临时文件
            FileUtil.del(videoFile); //删除视频源文件
            FileUtil.del(fileUploadDir + File.separator + fileMd5); //删除分段ts视频
            return R.fail("上传视频失败");
        }
    }

    /**
     * 获取当前目录下的分片文件数量
     *
     * @param directoryPath
     * @return
     */
    private int getChunkCount(String directoryPath) {
        File directory = new File(directoryPath);
        if (!directory.exists() || !directory.isDirectory()) {
            return 0;
        }
        File[] files = directory.listFiles((dir, name) -> name.endsWith(".tem"));
        return files != null ? files.length : 0;
    }

    /**
     * 合并分片
     *
     * @param uuid
     * @return
     */
    public void merge(String uuid, String fileUploadTempDir, String fileUploadDir) throws IOException {
        File dirFile = new File(fileUploadTempDir + File.separator + uuid);
        //分片上传的文件已经位于同一个文件夹下，方便寻找和遍历（当文件数大于十的时候记得排序用冒泡排序确保顺序是正确的）
        String[] fileNames = dirFile.list();
        Arrays.sort(fileNames, (o1, o2) -> {
            int i1 = Integer.parseInt(o1.substring(o1.indexOf(uuid) + uuid.length()).split("\\.tem")[0]);
            int i2 = Integer.parseInt(o2.substring(o2.indexOf(uuid) + uuid.length()).split("\\.tem")[0]);
            return i1 - i2;
        });

        //创建空的合并文件,以未见md5为文件名
        File targetFile = new File(fileUploadDir+File.separator+uuid+File.separator, uuid + ".mp4");
        if (!targetFile.getParentFile().exists()) {
            targetFile.getParentFile().mkdirs();
        }
        RandomAccessFile writeFile = new RandomAccessFile(targetFile, "rw");
        long position = 0;
        for (String fileName : fileNames) {
            System.out.println(fileName);
            File sourceFile = new File(fileUploadTempDir + File.separator + uuid, fileName);
            RandomAccessFile readFile = new RandomAccessFile(sourceFile, "rw");
            int chunksize = 1024 * 3;
            byte[] buf = new byte[chunksize];
            writeFile.seek(position);
            int byteCount;
            while ((byteCount = readFile.read(buf)) != -1) {
                if (byteCount != chunksize) {
                    byte[] tempBytes = new byte[byteCount];
                    System.arraycopy(buf, 0, tempBytes, 0, byteCount);
                    buf = tempBytes;
                }
                writeFile.write(buf);
                position = position + byteCount;
            }
            readFile.close();
        }
        writeFile.close();
        FileUtil.del(dirFile);
    }


    public static long getVideoDuration(String videoFilePath) throws Exception {
        FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(videoFilePath);
        grabber.start();
        long duration = grabber.getLengthInTime() / 1000; // in milliseconds
        grabber.stop();
        grabber.release();


        int totalSegments = calculateTotalSegments(duration); // 计算切片数量
        long segmentDuration = duration / totalSegments;
        return segmentDuration;
    }

    private static int calculateTotalSegments(long totalDuration) {
        // 根据每个切片时长为一分钟计算切片数量
        int segmentLengthInSeconds = 60; // 每个切片时长为一分钟（60秒）
        return (int) Math.ceil(totalDuration / (double) (segmentLengthInSeconds * 1000));
    }

    // 将视频文件转换为m3u8格式（Windows）
// 将视频文件转换为m3u8格式（Windows）
    public static void convertToM3U8ForWindows(String videoFilePath, String outputDir, long segmentDuration) {
        // 构建FFmpeg命令
        String[] command = {
            "ffmpeg",
            "-i", videoFilePath,
            "-c:v", "libx264",
            "-hls_time", String.valueOf(segmentDuration / 1000), // in seconds
            "-hls_list_size", "0",
            "-f", "hls",
            outputDir +File.separator+ "output.m3u8"
        };

        // 执行命令
        ProcessBuilder pb = new ProcessBuilder(command);
        pb.redirectErrorStream(true); // 将错误流重定向到标准输出
        try {
            Process process = pb.start();
            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line); // 打印命令执行过程中的输出信息
            }
            process.waitFor(); // 等待命令执行完成
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }


    // 将视频文件转换为m3u8格式（Linux）
    public static void convertToM3U8ForLinux(String videoFilePath, String outputDir, long segmentDuration) {
        // 构建FFmpeg命令
        String[] command = {
            "ffmpeg",
            "-i", videoFilePath,
            "-c:v", "libx264",
            "-hls_time", String.valueOf(segmentDuration / 1000), // in seconds
            "-hls_list_size", "0",
            "-f", "hls",
            outputDir + "/output.m3u8"
        };

        // 执行命令
        ProcessBuilder pb = new ProcessBuilder(command);
        try {
            Process process = pb.start();
            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line); // 打印命令执行过程中的输出信息
            }
            process.waitFor();
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }

//    /**
//     * 视频拆分
//     *
//     * @param inputFilePath   D:/home/dxhh/uploadPath/video/md5.mp4
//     * @param outputDirectory D:/home/dxhh/uploadPath/video/md5
//     */
//    @Async
//    public void mp4ToM3u8ForWindow(String fileMd5, String inputFilePath, String outputDirectory, long ms) throws IOException {
//        File uploadFile = new File(outputDirectory);
//        if (!uploadFile.exists()) {
//            uploadFile.mkdirs();
//        }
//        Path outputDirPath = Paths.get(outputDirectory);
//        //我的ffmpeg.exe放在 项目的/resources/script目录下
//        Path resourcePath = Paths.get("./script/ffmpeg.exe");
//        FFmpeg.atPath(resourcePath.getParent())
//            .addInput(UrlInput.fromPath(Paths.get(inputFilePath)))
//            .addOutput(UrlOutput.toPath(outputDirPath.resolve("output_%03d.ts")))
//            .addArguments("-f", "segment")
//            .addArguments("-segment_time", ms + "ms") // 分片时长为30s
//            .addArguments("-segment_list", outputDirPath.resolve("playlist.m3u8").toString())
//            .addArguments("-c:v", "copy") // 优化视频编码参数
//            .addArguments("-c:a", "copy") // 优化音频编码参数
//            .execute();
//        // 修改生成的m3u8文件，将ts链接替换为完整URL
//        updateM3u8File(fileMd5, outputDirectory);
//    }
//
//    /**
//     * 视频拆分
//     *
//     * @param fileMd5         adw1dwdadadwdadasd
//     * @param inputFilePath   /home/dxhh/uploadPath/video/md5.mp4
//     * @param outputDirectory /home/dxhh/uploadPath/video/md5
//     * @throws IOException
//     * @throws InterruptedException
//     */
//    public void mp4ToM3u8ForLinux(String fileMd5, String inputFilePath, String outputDirectory, long ms) throws IOException, InterruptedException {
//        String command = "ffmpeg -i " + inputFilePath + " -c copy -map 0 -f segment -segment_time " + ms + "ms -segment_list " + outputDirectory + "/playlist.m3u8 " + outputDirectory + "/output_%03d.ts";
//        //ffmpeg -i /home/dxhh/uploadPath/video/md5.mp4 -c copy -map 0 -f segment -segment_time 1236ms -segment_list /home/dxhh/uploadPath/video/md5/playlist.m3u8 /home/dxhh/uploadPath/video/md5/output_%03d.ts
//        log.info("视频分割脚本：{}", command);
//        ProcessBuilder builder = new ProcessBuilder(command.split(" "));
//        builder.redirectErrorStream(true);
//        Process process = builder.start();
//        BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
//        String line;
//        while ((line = reader.readLine()) != null) {
//            System.out.println(line);
//        }
//        int exitCode = process.waitFor();
//        if (exitCode == 0) {
//            System.out.println("FFmpeg command executed successfully");
//            updateM3u8File(fileMd5, outputDirectory);
//        } else {
//            System.out.println("FFmpeg command failed with exit code " + exitCode);
//        }
//    }
//
//    private void updateM3u8File(String fileMd5, String outputDirectory) throws IOException {
//        String m3u8FilePath = outputDirectory + "/playlist.m3u8";
//        List<String> lines = Files.readAllLines(Paths.get(m3u8FilePath));
//        List<String> newLines = new ArrayList<>();
//        for (String line : lines) {
//            if (line.endsWith(".ts")) {
//                if ("dev".equals(active)) {
//                    newLines.add("/dev-api/profile/video/" + fileMd5 + "/" + line);
//                } else {
//                    newLines.add("/stage-api/profile/video/" + fileMd5 + "/" + line);
//                }
//            } else {
//                newLines.add(line);
//            }
//        }
//        Files.write(Paths.get(m3u8FilePath), newLines);
//    }
//
//    public long getTsDuration(String filePath) throws EncoderException {
//        int targetSize = 2 * 1024 * 1024; // 2MB
//        File videoFile = new File(filePath);
//        long fileSize = videoFile.length();
//
//        Encoder encoder = new Encoder();
//        MultimediaInfo multimediaInfo = encoder.getInfo(videoFile);
//        long duration = multimediaInfo.getDuration();
//        System.out.println("Duration: " + duration + " ms");
//        System.out.println("File size: " + fileSize + " bytes");
//
//        // Calculate target duration for a 2MB video
//        long targetDuration = (duration * targetSize) / fileSize;
//
//        System.out.println("Target duration for a 2MB video: " + targetDuration + " ms");
//        return targetDuration;
//    }

}
